// Run module
#include "burner.h"
#include "maphkeys.h"

int runPause = 0;
int autoFrameSkip = 0;
int appVirtualFps = 6000;				// App fps * 100

bool showFPS = false;
static unsigned int doFPS = 0;
wchar_t fpsString[16];

static unsigned int normalLast = 0;		// Last value of timeGetTime()
static int normalFrac = 0;				// Extra fraction we did

bool appDoStep = 0;
bool appDoFast = 0;
unsigned int fastSpeed = 2;

static inline void displayFPS()
{
	static time_t fpsTimer;
	static unsigned int previousFrames;
	float burnFps = (float)BurnGetRefreshRate() / 100;

	time_t tempTime = clock();
	double fps = (double)(GetFramesEmulated() - previousFrames) * CLOCKS_PER_SEC / (tempTime - fpsTimer);
	_snwprintf(fpsString, sizearray(fpsString), L"%2.2lf/%2.2lf", fps, burnFps);
	vidSNewShortMsg(fpsString, 0xdfdfff, 480, 0);

	fpsTimer = tempTime;
	previousFrames = GetFramesEmulated();

	// display fps on window title
	scrnTitle();
}

static int getInput(bool copy)
{
	static int i = 0;
	inputInterface.make(copy);		// get input

	// FBA Combo - extra players input hack
	if (inputSwap && !kNetGame && copy) {
		BurnDrvInputSwap();
	}

	// Update Input dialog ever 3 frames
	if (i++ == 0) {
		inpdUpdate();
	}

	if (i >= 3) {
		i = 0;
	}

	// Update Input Set dialog
	inpsUpdate();
	return 0;
}

// update inputs
static int runUpdateInput(bool pause)
{
	if (pause) {
		getInput(false);				// Update burner inputs, but not game inputs
		return 0;
	}

	if (kNetGame) {
		getInput(true);					// Update inputs

		if (kailleraGetInput()) {		// Synchronize input with Kaillera
			return 0;
		}

		// FBA Combo - extra players input hack
		if (inputSwap) {
			BurnDrvInputSwap();
		}
	} else {
		if (replayStatus == 2) {
			getInput(false);			// Update burner inputs, but not game inputs

			if (replayInput()) {		// Read input from file
				setPauseMode(true);		// Auto pause after replay
				menuSync(MENUT_FILE);
				inputInterface.setCooperativeLevel(false, false);
			}
		} else {
			getInput(true);				// Update inputs
		}
	}

	if (replayStatus == 1) {
		recordInput();					// Write input to file
	}

	return 0;
}

// run one frame.
// If draw is true, it's the last frame before we are up to date, and so we should draw the screen
static int runFrame(int draw, bool pause)
{
	static int prevDraw = 0;

	if (prevDraw && !pause) {
		videoInterface.paint(0);		// paint the screen (no need to validate)
	}

	if (!driverOkay && !showEffect) {
		return 1;
	}

	if (pause) {
		// do something when in pause
		runUpdateInput(true);
	} else {
		SetFramesEmulated(GetFramesEmulated() + 1);
		SetCurrentFrame(GetCurrentFrame() + 1);

		runUpdateInput(false);

		if (draw) {
			SetFramesRendered(GetFramesRendered() + 1);

			if (videoInterface.frame()) {	// Do one frame
				audioInterface.blank();
			}
		} else {							// frame skipping
			pBurnDraw = NULL;				// Make sure no image is drawn
			if (driverOkay || !showEffect) {
				BurnDrvFrame();
			}
		}

		if (showFPS) {
			if (doFPS < GetFramesRendered()) {
				displayFPS();
				doFPS = GetFramesRendered() + 60;
			}
		}
	}

	if (aviStatus) {
		if (aviRecordFrame(draw)) {
			aviStop();
		}
	}

	prevDraw = draw;

	return 0;
}

// Callback used when needs more sound
static int runGetNextSound(int draw)
{
	if (audNextSound == NULL) {
		return 1;
	}

	if (runPause) {
		if (appDoStep) {
			runFrame(draw, false);
			audWriteSlience();
		} else {
			runFrame(draw, true);
		}

		appDoStep = 0;					// done one step
		return 0;
	}

	if (appDoFast) {					// do more frames
		for (unsigned int i = 0; i < fastSpeed; i++) {
			runFrame(0, false);
		}
	}

	// Render frame with sound
	pBurnSoundOut = audNextSound;
	runFrame(draw, false);

	// Log wave file
	waveLogWrite();

	if (appDoStep) {
		audWriteSlience();
	}
	appDoStep = 0;						// done one step

	return 0;
}

int runIdle()
{
	if (audioInterface.audPlaying) {
		// Run with sound
		audioInterface.check();
		return 0;
	}

	// Run without sound
	int time = timeGetTime() - normalLast;
	int count = (time * appVirtualFps - normalFrac) / 100000;
	if (count <= 0) {					// No need to do anything for a bit
		Sleep(2);
		return 0;
	}

	normalFrac += count * 100000;
	normalLast += normalFrac / appVirtualFps;
	normalFrac %= appVirtualFps;

	if (appDoFast){						// Temporarily increase virtual fps
		count += fastSpeed * 10;
	}
	if (count > 100) {					// Limit frame skipping
		count = 100;
	}

	if (runPause) {
		if (appDoStep) {				// Step one frame
			count = 10;
		} else {
			runFrame(1, true);			// Paused
			return 0;
		}
	}
	appDoStep = 0;

	for (int i = count / 10; i > 0; i--) {
		runFrame(autoFrameSkip, false);	// Mid-frames
	}
	runFrame(1, false);					// End-frame

	return 0;
}

int runReset()
{
	// Reset the speed throttling code
	normalLast = 0;
	normalFrac = 0;
	// Reset FPS display
	doFPS = 0;

	if (!audioInterface.audPlaying) {
		// run without sound
		normalLast = timeGetTime();
	}
	return 0;
}

int runInit()
{
	// Try to run with sound
	audioInterface.set(runGetNextSound);
	audioInterface.play();

	runReset();

	return 0;
}

int runExit()
{
	normalLast = 0;
	// Stop sound if it was playing
	audioInterface.stop();
	return 0;
}

static int runKeyMsg(const MSG& msg)
{
	if (msg.message == WM_SYSKEYDOWN || msg.message == WM_KEYDOWN) {
		return MHkeysDownHandle(msg);
	} else {
		if (msg.message == WM_SYSKEYUP || msg.message == WM_KEYUP) {
			return MHkeysUpHandle(msg);
		}
	}

	return 0;
}

// The main message loop
int runMessageLoop()
{
	MSG msg;

	// Remove pending initialisation messages from the queue
	while (PeekMessage(&msg, NULL, WM_APP + 0, WM_APP + 0, PM_NOREMOVE)) {
		if (msg.message != WM_QUIT) {
			PeekMessage(&msg, NULL, WM_APP + 0, WM_APP + 0, PM_REMOVE);
		}
	}

	while (1) {
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			// A message is waiting to be processed
			if (msg.message == WM_QUIT) {			// Quit program
				break;
			}

			// Check for messages for dialogs etc.
			if (!appMessage(&msg)) {
				continue;
			}

			// process key message
			if (runKeyMsg(msg)) {
				continue;
			}

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		} else {
			// No messages are waiting
			splashDestroy(0);
			runIdle();
		}
	}

	runExit();
	mediaExit();
	return 0;
}
